package jEDF.PreciseScoring;

import java.awt.*;
import java.awt.event.*;
import java.io.*;

import javax.swing.*;
import javax.swing.text.*;

import jEDF.EDF.*;
import jEDF.Exceptions.*;
import jEDF.Hypnogram.*;
import jEDF.JEDF.*;

/**
 *
 * <p>Titre : jEDF</p>
 * <p>Description : Java European Data Format Viewer and Analyser</p>
 * <p>Author : Nizar Kerkeni</p>
 * <p>Copyright : Copyright (c) 2003-2006</p>
 * <p>Version 2.0</p>
 */

public class PreciseScoring extends JPanel {

    private final int strLength = 100;
    private RandomAccessFile raf = null;
    private JEDFHypnogram hypnogram = null;
    private Polysomnograph polysomnograph = null;
    private JEDFMainWindow mainWindow = null;

    private File psFile = null;

    private final String unscoredText = "Unscored";

    private int currentPosition = 0; // in seconds, relative to the current hypnogram epoch duration
    private int granularity = 0; // in seconds

    // whether or not this score can be modified throughout keyboard shortcuts
    private boolean hasFocus = false;

    public PreciseScoring(JEDFMainWindow mainWindow,
                          Polysomnograph polysomnograph, JEDFHypnogram hypnogram) {
        this.hypnogram = hypnogram;
        this.mainWindow = mainWindow;
        this.polysomnograph = polysomnograph;

        this.setBackground(Color.WHITE);

        polysomnograph.setPreciseScoring(this);
        hypnogram.setPreciseScoring(this);
        addMouseMotionListener(new MouseMotionAdapter() {
            public void mouseMoved(MouseEvent arg0) {
                onMouseMove(arg0);
            }
        });
    }

    private void onMouseMove(MouseEvent me) {
        float x = me.getX();

        float width = (float) SwingUtilities.calculateInnerArea(this,
                new Rectangle()).getWidth();
        float coefWidth = width /
                          ((float) polysomnograph.getEpochDuration() /
                           (float) granularity);
        x /= coefWidth;
        String str = getScore((polysomnograph.getStartSecond() / granularity) +
                              (int) x);
        if (str != null) {
            setToolTipText(str.trim());
        }
    }

    /**
     * @param edfFile the edf file to create this score from
     * @param file the file to write this score to
     * @param granularity the wanted granularity, in seconds i.e. 2
     * @throws HypnogramNotLoadedException if there is no currently loaded hypnogram
     * @throws IOException if the specified file cannot be accessed
     */
    public void createPreciseScoring(EDFFile edfFile, File file, int granularity)
            throws HypnogramNotLoadedException, IOException {
        if (hypnogram.isCleared()) {
            throw new HypnogramNotLoadedException();
        }
        clear();

        this.psFile = file;
        raf = new RandomAccessFile(file, "rwd");
        this.granularity = granularity;
        // TODO verifier que granularity soit un multiple de hypnogram.getRecordDuration


        StringBuffer sb = null;

        // TODO verifier que le nom du fichier edf ne fasse pas plus de 100 caracteres
        sb = new StringBuffer(
                "Precise scoring file header : EDF file name / total duration / " +
                "record duration / granularity / data.");
        for (int i = sb.length(); i < strLength; i++) {
            sb.append(" ");
        }
        sb.append('\n');
        raf.writeBytes(sb.toString());

        String edfFileName = "EDF file : " + edfFile.getFileName();
        edfFileName = edfFileName.length() > strLength ?
                      edfFileName.substring(0, strLength) : edfFileName;
        sb = new StringBuffer(edfFileName);
        for (int i = sb.length(); i < strLength; i++) {
            sb.append(" ");
        }
        sb.append('\n');
        raf.writeBytes(sb.toString());

        sb = new StringBuffer("Total duration : " + edfFile.getTotalDuration());
        for (int i = sb.length(); i < strLength; i++) {
            sb.append(" ");
        }
        sb.append('\n');
        raf.writeBytes(sb.toString());

        sb = new StringBuffer("Record duration : " + hypnogram.getEpochDuration());
        for (int i = sb.length(); i < strLength; i++) {
            sb.append(" ");
        }
        sb.append('\n');
        raf.writeBytes(sb.toString());

        sb = new StringBuffer("Granularity : " + granularity);
        for (int i = sb.length(); i < strLength; i++) {
            sb.append(" ");
        }
        sb.append('\n');
        raf.writeBytes(sb.toString());

        fillNewPreciseScoringFile(edfFile, raf);
    }

    private void fillNewPreciseScoringFile(EDFFile edfFile, RandomAccessFile raf)
            throws IOException {
        StringBuffer sb = new StringBuffer(getStateString(hypnogram.getValue(0)));
        for (int j = sb.length(); j < strLength; j++) {
            sb.append(" ");
        }
        sb.append('\n');

        int period = hypnogram.getEpochDuration() / granularity;
        int j = 1;
        int k = 1;
        for (int i = 0; i < (edfFile.getTotalDuration() / granularity); i++) {
            if (j > period) {
                sb = new StringBuffer(getStateString(hypnogram.getValue(k)));
                for (int l = sb.length(); l < strLength; l++) {
                    sb.append(" ");
                }
                sb.append('\n');
                j = 1;
                if (k < hypnogram.getNbRecords() - 1) {
                    k++;
                }
            }
            j++;
            raf.writeBytes(sb.toString());
        }
    }

    /**
     * @param file the file to load
     * @throws HypnogramNotLoadedException if there is no hypnogram loaded
     * @throws IOException if the specified file cannot be accessed
     */
    public void loadPreciseScoring(File file)
            throws HypnogramNotLoadedException, IOException {
        if (hypnogram.isCleared()) {
            throw new HypnogramNotLoadedException();
        }

        clear();
        this.psFile = file;
        raf = new RandomAccessFile(file, "rwd");

        raf.seek(4 * (strLength + 1));
        try {
            granularity = new Integer(raf.readLine().substring(13, strLength).
                                      trim()).intValue();
        }
        catch (NullPointerException e) {
            raf = null;
            throw new IOException();
        }
    }

    /**
     * save the file and eliminate useless spaces
     * @param file the output file
     * @param dialog PreciseScoringToTextDialog
     */
    public void saveToTextFile(File file, PreciseScoringToTextDialog dialog) {
        String str = null;
        try {
            RandomAccessFile inputRaf = new RandomAccessFile(psFile, "r");
            RandomAccessFile outputRaf = new RandomAccessFile(file, "rw");
            while ((str = inputRaf.readLine()) != null) {
                outputRaf.writeBytes(str.trim()+"\n");
            }
            inputRaf.close();
            outputRaf.close();
        }
        catch (IOException ex) {
            JOptionPane.showMessageDialog(null, ex.getLocalizedMessage(),
                                          JEDFMainWindow.getLocalizedString("Error"),
                                          JOptionPane.ERROR_MESSAGE);
        }
        dialog.dispose();
    }

    private void setScore(String score, int position) {
        if (!isWritable()) {
            return;
        }

        StringBuffer sb = new StringBuffer(score);
        for (int i = sb.length(); i < strLength; i++) {
            sb.append(" ");
        }
        sb.append('\n');

        try {
            raf.seek((5 + position) * (strLength + 1));
            raf.writeBytes(sb.toString());
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * @param score set the current score
     */
    public void setScore(String score) {
        if (!hasFocus()) {
            return;
        }

        int position = polysomnograph.getStartSecond() + currentPosition;
        position /= granularity;
        setScore(score, position);
        nextPosition();
    }

    /**
     * @param position the position in file to get the score from (0..n-1)
     * @return the wanted score
     */
    private String getScore(int position) {
        String result = "";

        if (raf == null) {
            return result;
        }

        try {
            raf.seek((5 + position) * (strLength + 1));
            result = raf.readLine();
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }

    /**
     * @return the current score
     */
    public String getScore() {
        int position = polysomnograph.getStartSecond() + currentPosition;
        position /= granularity;
        return getScore(position);
    }

    /**
     * @return true if this PreciseScoring is cleared, i.e. has not associated file
     */
    public boolean isCleared() {
        return raf == null;
    }

    /**
     * clear this PreciseScoring
     */
    public void clear() {
        if (raf != null) {
            try {
                raf.close();
            }
            catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            raf = null;
        }
        currentPosition = 0;
        granularity = 0;
        hasFocus = false;
    }

    /* (non-Javadoc)
     * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
     */
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        if (isCleared()) {
            return;
        }

        if (hypnogram.getEpochDuration() == 0) {
            return;
        }

        Graphics2D g2D = (Graphics2D) g;

        float width = (float) SwingUtilities.calculateInnerArea(this,
                new Rectangle()).getWidth();
        float height = (float) SwingUtilities.calculateInnerArea(this,
                new Rectangle()).getHeight();
        float coefWidth = width /
                          ((float) polysomnograph.getEpochDuration() /
                           (float) granularity);

        FontMetrics fontMetrics = g2D.getFontMetrics();

        float fontHeight = fontMetrics.getHeight();
        fontHeight = (float) (((double) height - (double) fontHeight) / 2.0);

        float fontWidth = 0;

        int j = 0;
        for (float i = 0; i < width; i += coefWidth) {
            int position = (j +
                            (hypnogram.getCurrentRecord() *
                             (hypnogram.getEpochDuration()
                              /
                              granularity))); //(startPosition/granularity));

            String str = getScore(position);
            if (str == null) {
                str = "---";
            }
            else {
                str = str.trim();
            }

            str = trimToMaxDisplayableString(fontMetrics, str, coefWidth);

            fontWidth = fontMetrics.stringWidth(str);
            fontWidth = (float) (((double) coefWidth - (double) fontWidth) /
                                 2.0);

            if (((j * granularity) == currentPosition) && hasFocus()) {
                g2D.setColor(Color.RED);
            }
            else {
                g2D.setColor(Color.BLACK);
            }

            g2D.drawString(str, i + fontWidth, height - fontHeight);
            g2D.drawRect((int) i, 0, (int) coefWidth, (int) height);

            j++;
        }
    }

    private String trimToMaxDisplayableString(FontMetrics fontMetrics,
                                              String str, float coefWidth) {
        while (fontMetrics.stringWidth(str) > coefWidth) {
            str = str.substring(0, str.length() - 1);
        }
        return str;
    }


    /**
     * @return Returns the currentPosition.
     */
    public int getCurrentPosition() {
        return currentPosition;
    }

    /**
     * @return Returns the granularity.
     */
    public int getGranularity() {
        return granularity;
    }

    /**
     * @return true if the scores can be displayed
     */
    public boolean canBeDisplayed() {
        boolean result = true;

        result = result && !hypnogram.isCleared();
        result = result && !isCleared();

        return result;
    }

    private class InputDialog extends JDialog {
        private JTextField jTextField = new JTextField();

        public String getText() {
            String result = jTextField.getText();
            if (result.length() > strLength) {
                result = result.substring(0, strLength);
            }
            return result;
        }

        public InputDialog(JEDFMainWindow mainWindow, String text) {
            super(mainWindow, true);
            jTextField.setText(text.trim());
            JPanel jPanel = new JPanel(new BorderLayout());
            jTextField.setColumns((int) ((double) strLength * 0.75));
            jPanel.add(jTextField, BorderLayout.CENTER);

            setContentPane(jPanel);

            jTextField.setFocusable(true);

            Caret caret = jTextField.getCaret();

            caret.setDot(jTextField.getText().length());
            jTextField.setCaretPosition(0);

            jTextField.setSelectionStart(0);
            jTextField.setSelectionEnd(jTextField.getText().length());

            pack();
            setLocationRelativeTo(null);

            jTextField.addKeyListener(new KeyAdapter() {
                /* (non-Javadoc)
                 * @see java.awt.event.KeyAdapter#keyPressed(java.awt.event.KeyEvent)
                 */
                public void keyPressed(KeyEvent arg0) {
                    if (arg0.getKeyCode() == KeyEvent.VK_ENTER) {
                        dispose();
                    }
                }
            });
        }
    }


    /**
     * display a box for the user to type a custom label
     */
    public void setCustomText() {
        if (!hasFocus()) {
            return;
        }

        InputDialog inputDialog = new InputDialog(mainWindow, getScore());
        inputDialog.setVisible(true);
        setScore(inputDialog.getText());
    }

    /**
     * move to the next score
     */
    public void nextPosition() {
        if (isCleared()) {
            return;
        }

        if (!hasFocus()) {
            setFocus(true);
            return;
        }

        currentPosition += granularity;

        if (currentPosition >= polysomnograph.getEpochDuration() &&
            canBeDisplayed()) {
            resetPosition();
            setFocus(false);
            polysomnograph.incrementStartRecord();
        }

        positionChanged();
    }

    /**
     * move to the previous score
     */
    public void previousPosition() {
        if (isCleared()) {
            return;
        }

        if (!hasFocus()) {
            setFocus(true);
            return;
        }

        currentPosition -= granularity;
        if (currentPosition < 0) {
            resetPosition();
        }

        positionChanged();
    }

    /**
     * reset the current position to 0
     */
    public void resetPosition() {
        currentPosition = 0;
        positionChanged();
    }

    private void positionChanged() {
        polysomnograph.repaint();
        repaint();
    }


    /**
     * @param hasFocus true if this PreciseScoring is to accept keyboard inputs
     */
    public void setFocus(boolean hasFocus) {
        this.hasFocus = hasFocus
                        && !isCleared()
                        &&
                        (polysomnograph.getEpochDuration() ==
                         hypnogram.getEpochDuration());

        if (!this.hasFocus) {
            resetPosition();
        }
        else {
            repaint();
            polysomnograph.repaint();
        }
    }

    /**
     * @return true if this precise scoring accept keyboard inputs
     */
    public boolean hasFocus() {
        return hasFocus && !isCleared();
    }

    /*
     * mvt
     * s1
     * s2
     * s3
     * s4
     * rem (sp)
     * awake (veille)
     * cxk
     * spindle
     * vyo
     * vyf
     * art
     */



    /**
     * set the current state to K complex
     */
    public void setStateKCX() {
        setScore("K");
    }

    /**
     * set the current state to "veille, yeux ouverts"
     */
    public void setStateVYO() {
        setScore("Vyo");
    }

    /**
     * set the current state to "veille, yeux ferm�s"
     */
    public void setStateVYF() {
        setScore("Vyf");
    }

    /**
     * set the current state to artifact
     */
    public void setStateART() {
        setScore("Art");
    }

    /**
     * set the current state to "pointes vertex"
     */
    public void setStatePV() {
        setScore("Pv");
    }

    /**
     * set the current state to "fuseaux"
     */
    public void setStateFx() {
        setScore("Fx");
    }

    /**
     * set the current state to "saw tooth"
     */
    public void setStateST() {
        setScore("DS");
    }

    /**
     *
     * @param num int the text to assign to all the scores in the current epoch
     */
    public void setAll(int num) {
        String text = getStateString(num);
        int max = (polysomnograph.getStartSecond() +
                   polysomnograph.getEpochDuration());
        String score = "";
        for (int i = polysomnograph.getStartSecond(); i < max; i += granularity) {
            score = getScore(i / granularity).trim();
            if (score.compareTo(unscoredText) == 0) {
                setScore(text, i / granularity);
            }
        }
    }

    /**
     * Unscored 0
     * Awake 1
     * Rem 2
     * Stage1 3
     * Stage2 4
     * Stage3 5
     * Stage4 6
     * Movement 7
     * @param i 0..7
     */
    public void setHypnogramState(int i) {
        setScore(getStateString(i));
    }

    /**
     * @param i label indes
     * @return the long label
     */
    public String getStateString(int i) {
        String[] longLabels = {
                              "Unscored",
                              "Awake",
                              "Rem",
                              "Stage 1",
                              "Stage 2",
                              "Stage 3",
                              "Stage 4",
                              "Movement"};
        return longLabels[i];
    }

    private boolean isWritable() {
        return polysomnograph.getEpochDuration() == hypnogram.getEpochDuration();
    }

    public void duplicatePreviousScore() {
        if (!hasFocus()) {
            return;
        }

        if (currentPosition == 0) {
            return;
        }
        previousPosition();
        String previousScore = getScore();
        nextPosition();
        setScore(previousScore);
    }
}
